﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="DbContextBuilder.cs" company="">
//   
// </copyright>
// <summary>
//   The DbContextBuilder interface.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

#region

using System;
using System.Configuration;
using System.Data.Common;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration;
using System.Data.Objects;
using System.Reflection;

#endregion

namespace LGEVH.Infrastructure.Data.DbContexts
{
    /// <summary>
    /// The DbContextBuilder interface.
    /// </summary>
    /// <typeparam name="T">
    /// </typeparam>
    public interface IDbContextBuilder<T> where T : DbContext
    {
        /// <summary>
        /// The build db context.
        /// </summary>
        /// <returns>
        /// The <see cref="T"/>.
        /// </returns>
        T BuildDbContext();
    }

    /// <summary>
    /// The db context builder.
    /// </summary>
    /// <typeparam name="T">
    /// </typeparam>
    public class DbContextBuilder<T> : DbModelBuilder, IDbContextBuilder<T> where T : DbContext
    {
        /// <summary>
        /// The _cn string settings.
        /// </summary>
        private readonly ConnectionStringSettings _cnStringSettings;

        /// <summary>
        /// The _factory.
        /// </summary>
        private readonly DbProviderFactory _factory;

        /// <summary>
        /// The _lazy loading enabled.
        /// </summary>
        private readonly bool _lazyLoadingEnabled;

        /// <summary>
        /// The _recreate database if exists.
        /// </summary>
        private readonly bool _recreateDatabaseIfExists;

        /// <summary>
        /// Initializes a new instance of the <see cref="DbContextBuilder{T}"/> class.
        /// </summary>
        /// <param name="connectionStringName">
        /// The connection string name.
        /// </param>
        /// <param name="mappingAssemblies">
        /// The mapping assemblies.
        /// </param>
        /// <param name="recreateDatabaseIfExists">
        /// The recreate database if exists.
        /// </param>
        /// <param name="lazyLoadingEnabled">
        /// The lazy loading enabled.
        /// </param>
        public DbContextBuilder(string connectionStringName, string[] mappingAssemblies, bool recreateDatabaseIfExists, 
            bool lazyLoadingEnabled)
        {
            _cnStringSettings = ConfigurationManager.ConnectionStrings[connectionStringName];
            _factory = DbProviderFactories.GetFactory(_cnStringSettings.ProviderName);
            _recreateDatabaseIfExists = recreateDatabaseIfExists;
            _lazyLoadingEnabled = lazyLoadingEnabled;

            AddConfigurations(mappingAssemblies);
        }

        /// <summary>
        /// Creates a new <see cref="ObjectContext"/>.
        /// </summary>
        /// <param name="lazyLoadingEnabled">
        /// if set to <c>true</c> [lazy loading enabled].
        /// </param>
        /// <param name="recreateDatabaseIfExist">
        /// if set to <c>true</c> [recreate database if exist].
        /// </param>
        /// <returns>
        /// The <see cref="T"/>.
        /// </returns>
        public T BuildDbContext()
        {
            var cn = _factory.CreateConnection();
            cn.ConnectionString = _cnStringSettings.ConnectionString;

            var dbModel = Build(cn);

            ObjectContext ctx = dbModel.Compile().CreateObjectContext<ObjectContext>(cn);
            ctx.ContextOptions.LazyLoadingEnabled = _lazyLoadingEnabled;

            if (!ctx.DatabaseExists())
            {
                ctx.CreateDatabase();
            }
            else if (_recreateDatabaseIfExists)
            {
                ctx.DeleteDatabase();
                ctx.CreateDatabase();
            }

            return (T) new DbContext(ctx, true);
        }

        /// <summary>
        /// Adds mapping classes contained in provided assemblies and register entities as well
        /// </summary>
        /// <param name="mappingAssemblies">
        /// </param>
        private void AddConfigurations(string[] mappingAssemblies)
        {
            if (mappingAssemblies == null || mappingAssemblies.Length == 0)
            {
                throw new ArgumentNullException("mappingAssemblies", "You must specify at least one mapping assembly");
            }

            bool hasMappingClass = false;
            foreach (var mappingAssembly in mappingAssemblies)
            {
                Assembly asm = Assembly.LoadFrom(MakeLoadReadyAssemblyName(mappingAssembly));

                foreach (Type type in asm.GetTypes())
                {
                    if (type.BaseType.IsGenericType && IsMappingClass(type.BaseType))
                    {
                        hasMappingClass = true;

                        // http://areaofinterest.wordpress.com/2010/12/08/dynamically-load-entity-configurations-in-ef-codefirst-ctp5/
                        dynamic configurationInstance = Activator.CreateInstance(type);
                        Configurations.Add(configurationInstance);
                    }
                }
            }

            if (!hasMappingClass)
            {
                throw new ArgumentException("No mapping class found!");
            }
        }

        /// <summary>
        /// Determines whether a type is a subclass of entity mapping type
        /// </summary>
        /// <param name="mappingType">
        /// Type of the mapping.
        /// </param>
        /// <returns>
        /// <c>true</c> if it is mapping class; otherwise, <c>false</c>.
        /// </returns>
        private bool IsMappingClass(Type mappingType)
        {
            Type baseType = typeof (EntityTypeConfiguration<>);
            if (mappingType.GetGenericTypeDefinition() == baseType)
            {
                return true;
            }

            if ((mappingType.BaseType != null) &&
                !mappingType.BaseType.IsAbstract &&
                mappingType.BaseType.IsGenericType)
            {
                return IsMappingClass(mappingType.BaseType);
            }

            return false;
        }

        /// <summary>
        /// Ensures the assembly name is qualified
        /// </summary>
        /// <param name="assemblyName">
        /// </param>
        /// <returns>
        /// The <see cref="string"/>.
        /// </returns>
        private static string MakeLoadReadyAssemblyName(string assemblyName)
        {
            return (assemblyName.IndexOf(".dll") == -1)
                ? assemblyName.Trim() + ".dll"
                : assemblyName.Trim();
        }
    }
}